(function( $, undefined ) {
    function getInt(val) {
        try {
            return parseInt(val);
        } catch (e) {}
        return null;
    }
    function isNumber(val) {
        var s = getInt(val) + "";
        return (val === s);
    }
    var CALENDAR = {
        VAR: {
            DAY: 'day',
            MONTH: 'month',
            YEAR: 'year',
            HOUR: 'hour',
            MINUTE: 'minute',
            SECOND: 'second',
            SPLITTER: 'splitter'
        },
        FORMAT: {},
        getCalendar: null
    };
    CALENDAR.FORMAT= {
            PHP: {
                'd': { type: CALENDAR.VAR.DAY, size: 2 },
                'j': { type: CALENDAR.VAR.DAY, size: 1 },
                'm': { type: CALENDAR.VAR.MONTH, size: 2 },
                'n': { type: CALENDAR.VAR.MONTH, size: 1 },
                'Y': { type: CALENDAR.VAR.YEAR, size: 4 },
                'y': { type: CALENDAR.VAR.YEAR, size: 2 },
                'g': { type: CALENDAR.VAR.HOUR, size: 1, format: 12 },
                'G': { type: CALENDAR.VAR.HOUR, size: 1, format: 24 },
                'h': { type: CALENDAR.VAR.HOUR, size: 2, format: 12 },
                'H': { type: CALENDAR.VAR.HOUR, size: 2, format: 24 },
                'i': { type: CALENDAR.VAR.MINUTE, size: 2 },
                'i': { type: CALENDAR.VAR.SECOND, size: 2 }
            },
            JAVA: {
                'y': { type: CALENDAR.VAR.YEAR, size: 1 },
                'M': { type: CALENDAR.VAR.MONTH, size: 1 },
                'd': { type: CALENDAR.VAR.DAY, size: 1 },
                'H': { type: CALENDAR.VAR.HOUR, size: 1, format: 24 },
                'k': { type: CALENDAR.VAR.HOUR, size: 1, format: 12 },
                'm': { type: CALENDAR.VAR.MINUTE, size: 1 },
                's': { type: CALENDAR.VAR.SECOND, size: 1 }
            },
            JQUERY: {
                'd': { type: CALENDAR.VAR.DAY, size: 1 },
                'm': { type: CALENDAR.VAR.MONTH, size: 1 },
                'y': { type: CALENDAR.VAR.YEAR, size: 2 },
                'h': { type: CALENDAR.VAR.HOUR, size: 1, format: 24 },
                'i': { type: CALENDAR.VAR.MINUTE, size: 1 },
                's': { type: CALENDAR.VAR.SECOND, size: 1 }
            }
        };
    CALENDAR.getCalendar = function(format, type) {
            var retVal = new function() {
                this.date = new Date();
                this.dateVal = {};
                this.setDate = function(date, ignoreTime) {
                    this.dateVal[CALENDAR.VAR.YEAR] = date.getFullYear();
                    this.dateVal[CALENDAR.VAR.MONTH] = date.getMonth() + 1;
                    this.dateVal[CALENDAR.VAR.DAY] = date.getDate();
                    if (!ignoreTime) {
                        this.dateVal[CALENDAR.VAR.HOUR] = date.getHours();
                        this.dateVal[CALENDAR.VAR.MINUTE] = date.getMinutes();
                        this.dateVal[CALENDAR.VAR.SECOND] = date.getSeconds();
                    } else {
                        date.setHours(this.dateVal[CALENDAR.VAR.HOUR],
                                this.dateVal[CALENDAR.VAR.MINUTE],
                                this.dateVal[CALENDAR.VAR.SECOND]);
                    }
                    this.date = date;
                };
                this.roll = function(field, value) {
                    if (value === null)
                        value = 1;
                    this.dateVal[field] += value;
                    this.date.setFullYear(this.dateVal[CALENDAR.VAR.YEAR],
                            this.dateVal[CALENDAR.VAR.MONTH] -1,
                            this.dateVal[CALENDAR.VAR.DAY]);
                    this.date.setHours(this.dateVal[CALENDAR.VAR.HOUR],
                            this.dateVal[CALENDAR.VAR.MINUTE],
                            this.dateVal[CALENDAR.VAR.SECOND]);
                    if (field == CALENDAR.VAR.MONTH && this.date.getDate() < this.dateVal[CALENDAR.VAR.DAY]) {
                        this.date.setFullYear(this.dateVal[CALENDAR.VAR.YEAR],
                                this.dateVal[CALENDAR.VAR.MONTH] -1,
                                -1);
                        this.date.setMonth(this.dateVal[CALENDAR.VAR.MONTH] -1);
                    }
                    this.setDate(this.date);
                };
                this.set = function(key, value) {
                    if (value === null) {
                        for (var k in CALENDAR.VAR) {
                            if (CALENDAR.VAR[k] != CALENDAR.VAR.SPLITTER && key[k]) {
                                this.set(k, key[k]);
                            }
                        }
                    } else {
                        if (value == "") {
                            return;
                        }
                        value = getInt(value);
                        switch (key) {
                            case CALENDAR.VAR.YEAR:
                                this.date.setFullYear(value);
                                break;
                            case CALENDAR.VAR.MONTH:
                                this.date.setMonth(value -1);
                                break;
                            case CALENDAR.VAR.DAY:
                                this.date.setDate(value);
                                break;
                            case CALENDAR.VAR.HOUR:
                                this.date.setHours(value);
                                break;
                            case CALENDAR.VAR.MINUTE:
                                this.date.setMinutes(value);
                                break;
                            case CALENDAR.VAR.SECOND:
                                this.date.setHours(hours);
                                break;
                            default:
                                throw "Unknown key: " + key;
                        }
                        this.setDate(this.date);
                    }
                }
                this.getDate = function() { return this.date; }
                this.getDateVal = function() { return this.dateVal; }
                this.get = function(key) {return this.dateVal[key]; };
            };
            retVal.setDate(new Date);
            retVal.format_type = !type || type.length == 0 ? CALENDAR.FORMAT.JQUERY : CALENDAR.FORMAT[type.toUpperCase()];
            if (!retVal.format_type)
                retVal.format_type = CALENDAR.FORMAT.JQUERY;
            retVal.formatValues = [];
            for (var i = 0; i < format.length; i++) {
                var current = {type: CALENDAR.VAR.SPLITTER, value: ''};
                if (retVal.format_type[format[i]]) {
                    $.extend(current, retVal.format_type[format[i]]);
                } else {
                    current.value = format[i];
                    current.size = 1;
                }

                if (retVal.formatValues.length == 0 || retVal.formatValues[retVal.formatValues.length -1 ].type != current.type || retVal.formatValues[retVal.formatValues.length -1 ].format != current.format) {
                    retVal.formatValues.push(current);
                } else if (current.type == CALENDAR.VAR.SPLITTER) {
                    retVal.formatValues[retVal.formatValues.length -1 ].value += '' + current.value;
                    retVal.formatValues[retVal.formatValues.length -1 ].size += current.size;
                } else
                    retVal.formatValues[retVal.formatValues.length -1 ].size += current.size;
            }

            retVal.formatMap = {};
            for (var i = 0; i < retVal.formatValues.length; i++) {
                if (retVal.formatValues[i].type != CALENDAR.VAR.SPLITTER)
                    retVal.formatMap[retVal.formatValues[i].type] = i;
            }
            retVal.getFormattedDate = function() {
                var retVal = '';
                for (var k in this.formatValues) {
                    var f = this.formatValues[k];
                    if (f.type ==  CALENDAR.VAR.SPLITTER) {
                        retVal += f.value;
                    } else {
                        var v = this.get(f.type) + '';
                        while (v.length < f.size)
                            v = '0'+v;
                        if (f.type == CALENDAR.VAR.YEAR && v.length > f.size) {
                            v = v.substring(v.length - f.size, v.length);
                        }
                        retVal += '' + v;
                    }
                }
                return retVal;
            }
            retVal.setValue = function(value) {
                if (value == null || value == "") {
                    this.setDate(new Date);
                    return;
                }
                var values = []; 
                var index = 0;
                for (var i = 0; i < value.length; i++) {
                    var t = this.formatValues[index];
                    var v = value[i];
                    var fullVal = (values.length > index ? values[index] : "") + "" + v;
                    if (fullVal.length > t.size) {
                        index++;
                        t = this.formatValues[index];
                        fullVal = v;
                    }
                    if (t.type != CALENDAR.VAR.SPLITTER && !isNumber(v)) {
                        index++;
                        t = this.formatValues[index];
                        fullVal = v;
                    }
                    values[index] = fullVal;
                }
                if (values.length < this.formatValues.length)
                    return null;
                var retVal = {};
                for (var k in this.formatMap) {
                    retVal[k] = getInt(values[this.formatMap[k]]);
                }
                return retVal;
            }
            return retVal;
        };


    $.widget("ui.maskedDate", {
        options: {format: 'dd:mm:yy', debug: false},
        _create: function() {
            var self = this;
            var inputs = CALENDAR.getCalendar(this.options.format);
            var HTML = '';
            var renderCalButton = false;
            $.each(inputs.formatValues, function(index, obj) {
                if (obj.type == CALENDAR.VAR.SPLITTER) {
                    HTML += '<span class="masked-date-' + CALENDAR.VAR.SPLITTER + '">' + obj.value + '</span>';
                } else {
                    var length = obj.type == 'year' ? 4 : 2;
                    HTML += '<input type="text" class="masked-date-' + obj.type + '" size="' + length + '" maxlength="' + length + '"/>';
                    if (obj.type == CALENDAR.VAR.MONTH || obj.type == CALENDAR.VAR.DAY)
                        renderCalButton = true;
                }
            });
            if (renderCalButton) {
                HTML += '<button type="button" class="masked-date-datepicker ui-icon ui-icon-calendar">[...]</button>';
            }
            var container = $('<span class="masked-date-container">' + HTML + '</span>').insertBefore(this.element);
            var objects = {};
            $.each(inputs.formatMap, function(name, index) {
                objects[name] = container.find(".masked-date-" + name);
                objects[name].keypress(function(event) {
                   return self._keypress(name, objects[name], event); 
                });
                objects[name].change(function(event) {
                    return self._change(name, objects[name], event); 
                });
                if (renderCalButton === true && (name == CALENDAR.VAR.MONTH || name == CALENDAR.VAR.DAY))
                    renderCalButton = name;
            });
            this.element.change(function(event) {
                self._valueChange(event); 
            });
            this.container = $.extend({main: container, format: inputs}, objects);
            container.find("button.masked-date-datepicker").each(function() {
                var b = $(this);
                objects[renderCalButton].datepicker({
                    dateFormat: 'dd:mm:yy',
                    onSelect: function(t, inst) {
                        var d = objects[renderCalButton].datepicker("getDate");
                        if (d != null)
                            self.container.format.setDate(d, true);
                        self._fillFields(self.container.format.dateVal);
                        return false;
                    },
                    beforeShow: function() {
                        self._fillFields(self.container.format.dateVal);
                    },
                    onClose: function(dateText, inst) {
                        self._fillFields(self.container.format.dateVal);
                    },
                    changeMonth: true,
                    changeYear: true,
                    buttonImageOnly: true,
                    showOn: null,
                    constrainInput: false,
                    gotoCurrent: true
                });
                b.click(function() {
                    objects[renderCalButton].datepicker("show");
                    objects[renderCalButton].datepicker("setDate", self.container.format.date);
                    self._fillFields(self.container.format.dateVal);
                });
            });
            if (!this.options.debug)
                this.element.hide();
        },
        _setValue: function(newVal) {
            if (newVal == null)
                newVal = this.container.format.getFormattedDate();
            this.element.val(newVal);
        },
        _fillFields: function(values) {
            for (var k in this.container.format.formatMap) {
                this.container[k].val(values == null || values[k] == null ? "" : values[k]);
            }
            this._setValue(values != null ? null : "");
        },
        _keypress: function(name, container, event) {
            // numbers are OK
            if (event.charCode >= 48 && event.charCode <= 57)
                return true;
            // comma
            var d = this.container.format;
            d.set(name, container.val());
            switch (event.charCode) {
            case 46:
                return self.options.float && self.element.val().indexOf(".") < 0;
            }
            switch (event.keyCode) {
            case $.ui.keyCode.HOME:
            case $.ui.keyCode.END:
            case $.ui.keyCode.PAGE_UP:
            case $.ui.keyCode.PAGE_DOWN:
                return true;
            case $.ui.keyCode.UP:
                d.roll(name, 1);
                this._fillFields(d.dateVal);
                return true;
            case $.ui.keyCode.RIGHT:
                return true;
            case $.ui.keyCode.DOWN:
                d.roll(name, -1);
                this._fillFields(d.dateVal);
                return true;
            case $.ui.keyCode.LEFT:
            case $.ui.keyCode.BACKSPACE:
            case $.ui.keyCode.TAB:
            case $.ui.keyCode.DELETE:
            case $.ui.keyCode.ENTER:
                return true;

            }
            return false;
        },
        _validate: function(name, container) {
            var newVal = container.val();
            if (newVal != "") {
                this.container.format.set(name, newVal);
                this._fillFields(this.container.format.dateVal);
            } else {
                this._fillFields();
            }
            return true;
        },
        _change: function(name, container, event) {
            return this._validate(name, container);
        },
        _valueChange: function(event) {
            var values = this.container.format.setValue(this.element.val());
            this._fillFields(values);
            return true;
        }
    });
})( jQuery );